use std::{
    fs::File,
    io::{BufReader, BufWriter},
    vec,
};
use xml::{reader::XmlEvent, writer::XmlEvent as WEvent, EventReader};

fn main() -> std::io::Result<()> {
    let args = std::env::args();
    if args.len() < 2 {
        println!("wrong args");
        return Ok(());
    }
    let s = args.last().unwrap().clone();

    let read = BufReader::new(File::open(&s)?);

    let output = s
        .split_once(".xml")
        .map(|(m, _)| {
            let mut new_name = m.to_owned();
            new_name.push_str("_new.xml");
            new_name
        })
        .ok_or(std::io::ErrorKind::InvalidData)?;
    let write = BufWriter::new(File::create(output)?);
    let _ = sort_xml(read, write);

    Ok(())
}

fn sort_xml(_r: BufReader<File>, _w: BufWriter<File>) -> xml::writer::Result<()> {
    let f = EventReader::new(_r);

    let mut stack = vec![];
    let mut elements = vec![];
    for e in f {
        match e {
            Ok(XmlEvent::StartElement {
                name,
                mut attributes,
                ..
            }) => {
                stack.push(name.local_name.clone());
                attributes.sort_unstable_by(|a, b| a.name.local_name.cmp(&b.name.local_name));
                elements.push((stack.clone(), attributes.clone()));
            }
            Ok(XmlEvent::EndElement { .. }) => {
                stack.pop();
            }
            _ => {}
        }
    }

    elements.sort_by(|a, b| a.0.cmp(&b.0));

    let mut writer = xml::EmitterConfig::new()
        .perform_indent(true)
        .create_writer(_w);

    let mut high = 0;
    for (stack, attributes) in elements {
        for _ in stack.len()..=high {
            writer.write(WEvent::end_element())?;
        }
        high = stack.len();
        let mut start = WEvent::start_element(stack.last().unwrap().as_ref());
        for e in attributes.iter() {
            start = start.attr(e.name.local_name.as_ref(), &e.value);
        }

        writer.write(start)?;
    }
    for _ in 0..high {
        writer.write(WEvent::end_element())?;
    }

    Ok(())
}
